package com.pai4j.ugc.service.moment;
import com.pai4j.common.bean.PAIResponseBean;
import com.pai4j.common.bean.PAIResponseBeanUtil;
import com.pai4j.common.constants.ResourceBehaviorConstants;
import com.pai4j.common.constants.WebConstant;
import com.pai4j.common.enums.AccountRelationTypeEnum;
import com.pai4j.common.enums.MessageQueueTaskStatusEnum;
import com.pai4j.common.service.messagequeue.task.MessageQueueTaskDTO;
import com.pai4j.common.util.DateUtils;
import com.pai4j.common.util.JsonUtil;
import com.pai4j.common.util.RedisUtil;
import com.pai4j.common.util.TransactionSyncManagerUtil;
import com.pai4j.domain.vo.request.request.act.ResourceBehaviorRequestVO;
import com.pai4j.domain.vo.response.UserBaseResponseInfoVO;
import com.pai4j.domain.vo.response.moment.MomentDetailVO;
import com.pai4j.domain.vo.response.moment.MomentVO;
import com.pai4j.common.helper.SessionHelper;
import com.pai4j.ugc.repository.dao.IMomentDAO;
import com.pai4j.remote.act.IResourceBehaviorClient;
import com.pai4j.ugc.repository.dao.IMomentTimeLineDAO;
import com.pai4j.ugc.repository.entity.MomentTimeLineEntity;
import com.pai4j.ugc.service.AccountService;
import com.pai4j.ugc.service.UgcMessageQueueTaskService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import com.pai4j.common.enums.RedisKeyEnum;
import org.apache.commons.lang3.StringUtils;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;


@Slf4j
@Service
public class MomentTimeLineService {

    @Autowired
    private IMomentTimeLineDAO momentTimeLineDAO;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private AccountService accountService;
    @Autowired
    private MomentService momentService;
    @Autowired
    private IResourceBehaviorClient resourceBehaviorClient;
    @Autowired
    private UgcMessageQueueTaskService ugcMessageQueueTaskService;
    @Autowired
    private com.pai4j.remote.act.ICommentClient commentClient;
    @Autowired
    private IMomentDAO momentDAO;


    /**
     * 添加动态时间线
     * 数据一致性保证：使用事务同步管理器确保在事务提交后执行冷备操作
     *
     * @param author 动态作者
     * @param momentId 动态ID
     * @param time 发布时间
     * @param writeFriends 是否写入朋友时间线
     *
     * @author: CYM-pai
     * @date: 2025/01/04 10:00
     */
    @Transactional
    public void addTimeLine(String author, Long momentId, Long time, boolean writeFriends) {
        Set<String> friends = new HashSet<>();
        if (writeFriends) {
            friends = accountService.getAllSubscriberByAccount(author, AccountRelationTypeEnum.USER_USER);
        }
        friends.add(author);
        if (CollectionUtils.isEmpty(friends)) {
            return;
        }

        // 策略过滤不需要写入的用户
        // 1. 过滤掉已被作者拉黑的用户
        // 2. 过滤掉设置了隐私保护的用户
        // 3. 根据用户活跃度进行分层写入策略
        friends = this.filterTimelineUsers(author, friends);

        /**
         * 写扩散：双写 MySQL + 冷备
         */
        Date publishDate = new Date(time);
        // 持计划话朋友圈数据
        List<MomentTimeLineEntity> momentTimeLineEntities = friends.stream().map(u -> {
            MomentTimeLineEntity momentTimeLineEntity = new MomentTimeLineEntity();
            momentTimeLineEntity.setUserId(u);
            momentTimeLineEntity.setMomentId(momentId);
            momentTimeLineEntity.setTimeLine(publishDate);
            return momentTimeLineEntity;
        }).collect(Collectors.toList());
        this.saveMomentTimeLine(momentTimeLineEntities);

        // 异步写入冷备存储（MongoDB）- 确保数据一致性
        // 使用事务同步管理器，在事务提交后异步执行冷备操作
        Set<String> finalFriends = friends;
        TransactionSyncManagerUtil.registerSynchronization(() -> {
            try {
                this.saveMomentTimeLineToColdStorage(finalFriends, momentId, time);
            } catch (Exception e) {
                log.error("写入动态时间线冷备存储失败, momentId:{}, friends:{}", momentId, finalFriends, e);
                // 失败后加入消息队列重试
                this.addColdStorageRetryTask(finalFriends, momentId, time);
            }
        });

        // 写时间线缓存（用户）
        this.saveMomentTimeLineCache(friends, momentId);
        // 写公共时间线缓存
        this.savePublicTimelineCache(momentId);
    }


    /**
     * 朋友圈数据加载
     *
     * @param userId
     * @param lastMomentId
     * @param size
     * @return
     */
    public List<MomentDetailVO> timeline(String userId, Long lastMomentId, int size) {

        String timeLineKey = this.userTimelineCacheKey(userId);
        String noneCacheKey = this.userTimelineNoneHotCacheKey(userId);
        // 为什么这里需要noneCacheKey，原因为保证短时间只查询一次库
        // 无热点缓存存在说明：我们MySQL无3天内热点数据，但是可能存在3天外的冷备数据
        if (!redisUtil.exists(noneCacheKey) && !redisUtil.exists(timeLineKey)) {
            // BY用户时间线查询，不存在并发场景，所以这里不需要加锁
            this.refreshUserTimelineCache(userId);
        }
        Set<String> caches = redisUtil.zreverseRangeByScore(timeLineKey, lastMomentId - 1, 0, size);
        if (caches.size() < size) {
            // 说明redis缓存的数据不够显示
            // 1、本身用户朋友圈数据就是很少
            // 2、用户加载到中间页，redis3天内数据不够显示
            // 当前页数据不足{size}条，重新查询冷备（MongoDB）
            Set<String> coldStorageCaches = this.queryMomentsByMongoDB(userId, lastMomentId, size - caches.size());
            if (CollectionUtils.isNotEmpty(coldStorageCaches)) {
                caches.addAll(coldStorageCaches);
            }
        }
        if (CollectionUtils.isEmpty(caches)) {
            // mysql、redis、冷备都同时没查询到数据，说明数据已经加载完
            return Collections.emptyList();
        }
        List<Long> momentIds = caches.stream().map(Long::parseLong).collect(Collectors.toList());
        List<MomentVO> momentVOList = momentService.batchGetMomentFromCache(momentIds);
        Set<String> authorIds = momentVOList.stream().map(MomentVO::getAuthor).collect(Collectors.toSet());
        Map<String, UserBaseResponseInfoVO> authorInfoMap = accountService.batchGet(authorIds);

        // RPC 请求pai4j-act互动服务获取用户资源互动状态、资源互动数据
        Map<Long, Long> likeCountMap = this.batchGetLikeCount(momentIds);
        Map<Long, Boolean> userLikeStatusMap = this.batchGetUserLikeStatus(userId, momentIds);
        Map<Long, Long> commentCountMap = this.batchGetCommentCount(momentIds);

        return momentVOList.stream().map(m -> {
            MomentDetailVO momentDetail = new MomentDetailVO();
            BeanUtils.copyProperties(m, momentDetail);
            momentDetail.setAuthorInfo(authorInfoMap.get(m.getAuthor()));

            // 设置互动数据
            Long momentId = m.getId();
            momentDetail.setLikeCount(likeCountMap.getOrDefault(momentId, 0L));
            momentDetail.setIsLike(userLikeStatusMap.getOrDefault(momentId, false));
            momentDetail.setCommentCount(commentCountMap.getOrDefault(momentId, 0L));

            return momentDetail;
        }).collect(Collectors.toList());
    }

    /**
 * 公共时间线（不带userId），按动态ID倒序翻页
 * 修改为每次都从数据库查询并更新缓存
 *
 * @param lastMomentId 上一页最后一个动态ID
 * @param size 加载数量
 * @return 动态详情列表
 */
public List<MomentDetailVO> publicTimeline(Long lastMomentId, int size) {
    if (lastMomentId == null || lastMomentId <= 0) {
        lastMomentId = Long.MAX_VALUE;
    }

    String timeLineKey = this.publicTimelineCacheKey();
    String noneCacheKey = this.publicTimelineNoneHotCacheKey();

    // 直接从数据库查询数据
    org.springframework.data.domain.Pageable pageable = org.springframework.data.domain.PageRequest.of(0, size,
            org.springframework.data.domain.Sort.by(org.springframework.data.domain.Sort.Direction.DESC, "id"));
    org.springframework.data.domain.Page<com.pai4j.ugc.repository.entity.MomentEntity> page =
            momentDAO.findByIdLessThanOrderByIdDesc(lastMomentId, pageable);

    Set<String> caches = new LinkedHashSet<>();
    if (page != null && page.hasContent()) {
        java.util.List<com.pai4j.ugc.repository.entity.MomentEntity> entities = page.getContent();
        // 更新缓存
        Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<>();
        for (com.pai4j.ugc.repository.entity.MomentEntity e : entities) {
            String value = String.valueOf(e.getId());
            double score = e.getId();
            tuples.add(new DefaultTypedTuple<>(value, score));
        }
        if (!tuples.isEmpty()) {
            redisUtil.zadd(timeLineKey, tuples, RedisKeyEnum.MOMENT_TIME_LINE_CACHE.getExpireTime());
            redisUtil.remove(noneCacheKey);
            caches = entities.stream().map(e -> String.valueOf(e.getId())).collect(Collectors.toCollection(LinkedHashSet::new));
        }
    } else {
        // DB也无数据，标记none
        redisUtil.set(noneCacheKey, WebConstant.CACHE_NONE, RedisKeyEnum.MOMENT_TIME_LINE_NONE_HOT_DATE_CACHE.getExpireTime());
    }

    // 如果数据库查询的数据不足，从冷备查询补充
    if (caches.size() < size) {
        Set<String> coldStorageCaches = this.queryPublicMomentsByMongoDB(lastMomentId, size - caches.size());
        if (CollectionUtils.isNotEmpty(coldStorageCaches)) {
            caches.addAll(coldStorageCaches);
        }
    }

    if (CollectionUtils.isEmpty(caches)) {
        return Collections.emptyList();
    }

    List<Long> momentIds = caches.stream().map(Long::parseLong).collect(Collectors.toList());
    List<MomentVO> momentVOList = momentService.batchGetMomentFromCache(momentIds);
    Set<String> authorIds = momentVOList.stream().map(MomentVO::getAuthor).collect(Collectors.toSet());
    Map<String, UserBaseResponseInfoVO> authorInfoMap = accountService.batchGet(authorIds);

    Map<Long, Long> likeCountMap = this.batchGetLikeCount(momentIds);
    // 公共时间线下尝试获取当前登录用户，若无则默认未点赞
    String currentUserId = null;
    try { currentUserId = SessionHelper.getCurrentUserId(); } catch (Exception ignore) {}
    final Map<Long, Boolean> likeStatus = StringUtils.isNotBlank(currentUserId)
            ? this.batchGetUserLikeStatus(currentUserId, momentIds)
            : java.util.Collections.emptyMap();
    Map<Long, Long> commentCountMap = this.batchGetCommentCount(momentIds);

    return momentVOList.stream().map(m -> {
        MomentDetailVO momentDetail = new MomentDetailVO();
        BeanUtils.copyProperties(m, momentDetail);
        momentDetail.setAuthorInfo(authorInfoMap.get(m.getAuthor()));
        Long momentId = m.getId();
        momentDetail.setLikeCount(likeCountMap.getOrDefault(momentId, 0L));
        momentDetail.setIsLike(likeStatus.getOrDefault(momentId, false));
        momentDetail.setCommentCount(commentCountMap.getOrDefault(momentId, 0L));
        return momentDetail;
    }).collect(Collectors.toList());
}

    /**
     * 从MongoDB冷备存储查询动态时间线数据
     *
     * @param userId 用户ID
     * @param lastMomentId 最后一条动态ID
     * @param size 查询数量
     * @return 动态ID集合
     *
     * @author: CYM-pai
     * @date: 2025/01/04 10:30
     */
    private Set<String> queryMomentsByMongoDB(String userId, Long lastMomentId, int size) {
        try {
            // TODO: 实现MongoDB查询逻辑
            // 1. 连接MongoDB
            // 2. 查询用户时间线集合
            // 3. 按时间倒序查询，过滤掉已查询的数据
            // 4. 返回动态ID集合

            // 暂时返回空集合，避免空指针异常
            log.warn("MongoDB冷备查询暂未实现, userId:{}, lastMomentId:{}, size:{}", userId, lastMomentId, size);
            return new HashSet<>();
        } catch (Exception e) {
            log.error("MongoDB冷备查询失败, userId:{}, lastMomentId:{}, size:{}", userId, lastMomentId, size, e);
            return new HashSet<>();
        }
    }

    /**
     * 过滤时间线用户策略
     *
     * @param author 动态作者
     * @param friends 朋友列表
     * @return 过滤后的用户列表
     *
     * @author: CYM-pai
     * @date: 2025/01/04 10:35
     */
    private Set<String> filterTimelineUsers(String author, Set<String> friends) {
        try {
            // 1. 过滤掉已被作者拉黑的用户
            // 2. 过滤掉设置了隐私保护的用户
            // 3. 根据用户活跃度进行分层写入策略

            // 暂时返回原始列表，后续可根据业务需求完善过滤逻辑
            return friends.stream()
                    .filter(userId -> !StringUtils.isBlank(userId))
                    .collect(Collectors.toSet());
        } catch (Exception e) {
            log.error("过滤时间线用户失败, author:{}, friends:{}", author, friends, e);
            return friends;
        }
    }

    /**
     * 保存动态时间线到冷备存储
     *
     * @param friends 朋友列表
     * @param momentId 动态ID
     * @param time 发布时间
     *
     * @author: CYM-pai
     * @date: 2025/01/04 10:40
     */
    private void saveMomentTimeLineToColdStorage(Set<String> friends, Long momentId, Long time) {
        try {
            // TODO: 实现MongoDB冷备存储逻辑
            // 1. 构建冷备数据结构
            // 2. 批量写入MongoDB
            // 3. 设置过期时间（如6个月）

            log.info("保存动态时间线到冷备存储, momentId:{}, friends size:{}", momentId, friends.size());
        } catch (Exception e) {
            log.error("保存动态时间线到冷备存储失败, momentId:{}, friends:{}", momentId, friends, e);
            throw e;
        }
    }

    /**
     * 添加冷备存储重试任务
     *
     * @param friends 朋友列表
     * @param momentId 动态ID
     * @param time 发布时间
     *
     * @author: CYM-pai
     * @date: 2025/01/04 10:45
     */
    private void addColdStorageRetryTask(Set<String> friends, Long momentId, Long time) {
        try {
            // 构建重试任务DTO
            MessageQueueTaskDTO retryTask = new MessageQueueTaskDTO();
            retryTask.setTopicName("MOMENT_TIMELINE_COLD_STORAGE");
            retryTask.setTaskJson(JsonUtil.toJsonString(Map.of("friends", friends, "momentId", momentId, "time", time)));
            retryTask.setMethod("saveMomentTimeLineToColdStorage");
            retryTask.setStatus(MessageQueueTaskStatusEnum.ERROR.getStatus());

            // 保存重试任务到数据库
            ugcMessageQueueTaskService.saveFailTask2DB(retryTask);
            log.info("添加冷备存储重试任务成功, momentId:{}", momentId);
        } catch (Exception e) {
            log.error("添加冷备存储重试任务失败, momentId:{}, friends:{}", momentId, friends, e);
        }
    }

    /**
     * 批量获取动态点赞数量
     *
     * @param momentIds 动态ID列表
     * @return 动态ID -> 点赞数量映射
     *
     * @author: CYM-pai
     * @date: 2025/08/04 10:50
     */
    private Map<Long, Long> batchGetLikeCount(List<Long> momentIds) {
        Map<Long, Long> likeCountMap = new HashMap<>();
        try {
            for (Long momentId : momentIds) {
                ResourceBehaviorRequestVO request = new ResourceBehaviorRequestVO();
                request.setBehaviorType(ResourceBehaviorConstants.BehaviorType.LIKE.name());
                request.setResourceType(String.valueOf(ResourceBehaviorConstants.ResourceType.MOMENT));
                request.setResourceId(momentId);

                PAIResponseBean<Long> response = resourceBehaviorClient.countBehavior(request);
                if (PAIResponseBeanUtil.isOk(response)) {
                    likeCountMap.put(momentId, response.getData());
                } else {
                    likeCountMap.put(momentId, 0L);
                }
            }
        } catch (Exception e) {
            log.error("批量获取动态点赞数量失败, momentIds:{}", momentIds, e);
            // 降级处理：返回默认值
            momentIds.forEach(id -> likeCountMap.put(id, 0L));
        }
        return likeCountMap;
    }

    /**
     * 批量获取用户对动态的点赞状态
     *
     * @param userId 用户ID
     * @param momentIds 动态ID列表
     * @return 动态ID -> 是否点赞映射
     *
     * @author: CYM-pai
     * @date: 2025/08/04 10:55
     */
    private Map<Long, Boolean> batchGetUserLikeStatus(String userId, List<Long> momentIds) {
        Map<Long, Boolean> likeStatusMap = new HashMap<>();
        try {
            for (Long momentId : momentIds) {
                ResourceBehaviorRequestVO request = new ResourceBehaviorRequestVO();
                request.setUserId(userId);
                request.setBehaviorType(ResourceBehaviorConstants.BehaviorType.LIKE.name());
                request.setResourceType(String.valueOf(ResourceBehaviorConstants.ResourceType.MOMENT));
                request.setResourceId(momentId);

                PAIResponseBean<Boolean> response = resourceBehaviorClient.isBehavior(request);
                if (PAIResponseBeanUtil.isOk(response)) {
                    likeStatusMap.put(momentId, response.getData());
                } else {
                    likeStatusMap.put(momentId, false);
                }
            }
        } catch (Exception e) {
            log.error("批量获取用户点赞状态失败, userId:{}, momentIds:{}", userId, momentIds, e);
            // 降级处理：返回默认值
            momentIds.forEach(id -> likeStatusMap.put(id, false));
        }
        return likeStatusMap;
    }

    /**
     * 批量获取动态评论数量
     * 通过Redis缓存优化性能，避免频繁RPC调用
     *
     * @param momentIds 动态ID列表
     * @return 动态ID -> 评论数量映射
     *
     * @author: CYM-pai
     * @date: 2025/08/04 11:00
     */
    private Map<Long, Long> batchGetCommentCount(List<Long> momentIds) {
        Map<Long, Long> commentCountMap = new HashMap<>();
        try {
            PAIResponseBean<java.util.Map<Long, Long>> resp = commentClient.batchCount(momentIds, String.valueOf(ResourceBehaviorConstants.ResourceType.MOMENT));
            if (PAIResponseBeanUtil.isOk(resp) && resp.getData() != null) {
                commentCountMap.putAll(resp.getData());
            } else {
                momentIds.forEach(id -> commentCountMap.put(id, 0L));
            }
        } catch (Exception e) {
            log.error("批量获取动态评论数量失败, momentIds:{}", momentIds, e);
            momentIds.forEach(id -> commentCountMap.put(id, 0L));
        }
        return commentCountMap;
    }

    /**
     * 从资源行为服务获取评论数量
     * 如果评论不是通过资源行为统计，这里需要调用专门的评论服务
     *
     * @param momentId 动态ID
     * @return 评论数量
     *
     * @author: CYM-pai
     * @date: 2025/08/04 11:05
     */
    private Long getCommentCountFromBehavior(Long momentId) {
        try {
            // 方案1：如果评论是通过资源行为统计的
             ResourceBehaviorRequestVO request = new ResourceBehaviorRequestVO();
             // 假设有COMMENT行为类型
             request.setBehaviorType("COMMENT");
             request.setResourceType(String.valueOf(ResourceBehaviorConstants.ResourceType.MOMENT));
             request.setResourceId(momentId);
            PAIResponseBean<Long> response = resourceBehaviorClient.countBehavior(request);
            if (PAIResponseBeanUtil.isOk(response)) {
                 return response.getData();
             }
            return 0L;
        } catch (Exception e) {
            log.error("获取动态评论数量失败, momentId:{}", momentId, e);
            return 0L;
        }
    }

    private String userTimelineCacheKey(String userId) {

        return RedisKeyEnum.MOMENT_TIME_LINE_CACHE.getKey(userId);
    }

    private String userTimelineNoneHotCacheKey(String userId) {

        return RedisKeyEnum.MOMENT_TIME_LINE_NONE_HOT_DATE_CACHE.getKey(userId);
    }

    private String publicTimelineCacheKey() {
        return RedisKeyEnum.MOMENT_TIME_LINE_CACHE.getKey("PUBLIC");
    }

    private String publicTimelineNoneHotCacheKey() {
        return RedisKeyEnum.MOMENT_TIME_LINE_NONE_HOT_DATE_CACHE.getKey("PUBLIC");
    }

    /**
     * 删除用户无热点数据标识
     *
     */
    private void delUserTimelineNoneHotCache(Set<String> userIds) {

        List<String> noneCacheKeys = userIds.stream().map(this::userTimelineNoneHotCacheKey).collect(Collectors.toList());
        redisUtil.removeAll(noneCacheKeys);
    }

    /**
     * 刷新用户朋友圈时间线缓存
     *
     * @param userId
     */
    private void refreshUserTimelineCache(String userId) {

        // 计算3天前时间
        Date afterDate = DateUtils.getBeforeNDayDate(3);
        List<MomentTimeLineEntity> momentTimeLineEntities = momentTimeLineDAO.findAllByUserIdAndTimeLineAfter(userId, afterDate);
        if (CollectionUtils.isNotEmpty(momentTimeLineEntities)) {
            // 刷新时间线redis
            Set<ZSetOperations.TypedTuple<String>> typedTupleSet = new HashSet<>();
            for (MomentTimeLineEntity momentTimeLine : momentTimeLineEntities) {
                String value = momentTimeLine.getMomentId().toString();
                double score = momentTimeLine.getMomentId();
                ZSetOperations.TypedTuple<String> tuple = new DefaultTypedTuple<>(value, score);
                typedTupleSet.add(tuple);
            }
            String timeLineKey = this.userTimelineCacheKey(userId);
            redisUtil.zadd(timeLineKey, typedTupleSet, RedisKeyEnum.MOMENT_TIME_LINE_CACHE.getExpireTime());
        } else {
            String noneHotKey = RedisKeyEnum.MOMENT_TIME_LINE_NONE_HOT_DATE_CACHE.getKey(userId);
            redisUtil.set(noneHotKey, WebConstant.CACHE_NONE, RedisKeyEnum.MOMENT_TIME_LINE_NONE_HOT_DATE_CACHE.getExpireTime());
        }
    }

    /**
     * 刷新公共时间线缓存（近3天）
     */
    private void refreshPublicTimelineCache() {
        Date afterDate = DateUtils.getBeforeNDayDate(3);
        org.springframework.data.domain.Pageable pageable = org.springframework.data.domain.PageRequest.of(0, 1000,
                org.springframework.data.domain.Sort.by(org.springframework.data.domain.Sort.Direction.DESC, "id"));
        org.springframework.data.domain.Page<com.pai4j.ugc.repository.entity.MomentEntity> page =
                momentDAO.findByCreateDateAfter(afterDate, pageable);
        if (page != null && page.hasContent()) {
            Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<>();
            for (com.pai4j.ugc.repository.entity.MomentEntity e : page.getContent()) {
                String value = String.valueOf(e.getId());
                double score = e.getId();
                tuples.add(new DefaultTypedTuple<>(value, score));
            }
            redisUtil.zadd(this.publicTimelineCacheKey(), tuples, RedisKeyEnum.MOMENT_TIME_LINE_CACHE.getExpireTime());
            // 刷新缓存后，删除公共无热点标识
            redisUtil.remove(this.publicTimelineNoneHotCacheKey());
        } else {
            redisUtil.set(this.publicTimelineNoneHotCacheKey(), WebConstant.CACHE_NONE,
                    RedisKeyEnum.MOMENT_TIME_LINE_NONE_HOT_DATE_CACHE.getExpireTime());
        }
    }

    /**
     * 将新增动态写入公共时间线缓存
     */
    private void savePublicTimelineCache(Long momentId) {
        String key = this.publicTimelineCacheKey();
        if (!redisUtil.exists(key)) {
            // 首次访问或过期，主动刷新近3天数据
            this.refreshPublicTimelineCache();
        }
        redisUtil.zadd(key, String.valueOf(momentId), momentId,
                RedisKeyEnum.MOMENT_TIME_LINE_CACHE.getExpireTime());
        // 删除公共无热点标识，确保后续不会短期内误判不查询
        redisUtil.remove(this.publicTimelineNoneHotCacheKey());
    }

    /**
     * 从MongoDB冷备查询公共时间线（预留，暂未实现）
     */
    private Set<String> queryPublicMomentsByMongoDB(Long lastMomentId, int size) {
        try {
            log.warn("公共时间线MongoDB冷备查询暂未实现, lastMomentId:{}, size:{}", lastMomentId, size);
            return new HashSet<>();
        } catch (Exception e) {
            log.error("公共时间线MongoDB冷备查询失败, lastMomentId:{}, size:{}", lastMomentId, size, e);
            return new HashSet<>();
        }
    }

    /**
     * 写入用户朋友圈时间线缓存
     * 注意：这里redis sorted set中的score我们没有使用动态发布时间戳/写入时间线的时间戳，
     * 主要是避免并发场景下score相同导致用户端分页查询可能出现重复数据、丢数据问题。所以我们使用唯一动态id作为，同时保证顺序性。
     *
     * @param friends
     * @param momentId
     */
    private void saveMomentTimeLineCache(Set<String> friends, Long momentId) {

        List<String> keys = friends.stream().map( u ->
                RedisKeyEnum.MOMENT_TIME_LINE_CACHE.getKey(u)).collect(Collectors.toList());
        keys.forEach(key -> {
            if (!redisUtil.exists(key)) {

               this.refreshUserTimelineCache(key);
            }
            redisUtil.zadd(key, String.valueOf(momentId), momentId,
                    RedisKeyEnum.MOMENT_TIME_LINE_CACHE.getExpireTime());
        });
        // 删除用户无热点数据缓存
        this.delUserTimelineNoneHotCache(friends);
    }

    private void saveMomentTimeLine(List<MomentTimeLineEntity> momentTimeLineEntities) {
        if (CollectionUtils.isEmpty(momentTimeLineEntities)) {
            return;
        }
        momentTimeLineDAO.saveAll(momentTimeLineEntities);
    }


    public void save(String userId) {
        MomentTimeLineEntity entity = new MomentTimeLineEntity();

        entity.setUserId(userId);
        entity.setMomentId(1L);
        entity.setTimeLine(new Date());
        momentTimeLineDAO.save(entity);
    }


//    public static void main(String[] args) {
//        String userId = "U_1bb8a10f2d2a45a4b075c20016f10cb8";
//        int[] idxArr = new int[3];
//        for (int i = 0; i < 1000; i++) {
//
//            userId += i;
//            int idx = Math.abs(userId.hashCode()) % 3;
//            idxArr[idx] = idxArr[idx] + 1;
//        }
//        System.out.println("0:" + idxArr[0]);
//        System.out.println("1:" + idxArr[1]);
//        System.out.println("2:" + idxArr[2]);
//    }

}
